package net.pulseproject.router.modules.logic.beatstabilizer.old.approach2;

import java.util.Collection;
import java.util.Timer;

import javax.sound.midi.ShortMessage;

import net.pulseproject.commons.midi.Receivable;
import net.pulseproject.commons.midi.Transmitable;
import net.pulseproject.commons.midi.entity.ControllerMessage;
import net.pulseproject.commons.misc.TimerTaskWrapperX;
import net.pulseproject.commons.misc.TimerTaskWrapperX.TimerListener;
import net.pulseproject.commons.util.MidiUtil;
import net.pulseproject.router.modules.BiDirectionalModule;
import net.pulseproject.router.modules.logic.beatstabilizer.BeatStabilizerDebuggerGUiApp;
import net.pulseproject.router.modules.logic.beatstabilizer.old.approach2.OLD2_BeatHistory;
import net.pulseproject.router.modules.logic.beatstabilizer.old.approach2.OLD2_BeatTimeManager;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 
 * - we dont need to be in sync with real beat, important is just to keep up with the speed
 * - respect two things:
 *   1. keep public visibile speed changes smooth
 *   2. try to stick with real beat as good as possible (global min/max, local=timeframe min/max)
 */
public class OLD2_BeatStabilizerModule extends BiDirectionalModule implements TimerListener { // implements SimpleListener {
	
	static final Log LOG = LogFactory.getLog(OLD2_BeatStabilizerModule.class);
	
	private final OLD2_BeatHistory beatHistory = new OLD2_BeatHistory();
	private final OLD2_BeatTimeManager beatTimeManager;
	private Timer timer;

	private long timestampLastMessageSent = -1;
	
	public OLD2_BeatStabilizerModule(String id, Collection<Receivable> outgoingModules, long globalMinMs, int globalMaxMs, double frameDeviationPercent) {
		super(id, outgoingModules);
		LOG.debug("new BeatStabilizerModule(globalMinMs=" + globalMinMs + ", globalMaxMs=" + globalMaxMs + ", frameDeviationPercent=" + frameDeviationPercent + ")");
		this.beatTimeManager = new OLD2_BeatTimeManager(this.beatHistory, globalMinMs, globalMaxMs, frameDeviationPercent);
	}

	private final void startDelayWindowAndBroadcast(ShortMessage message, boolean isArtificialMessage) {
		LOG.debug("startDelayWindowAndBroadcast(message)");
		
		if(isArtificialMessage) {
			this.beatHistory.addBeat(); // TODO wirklich so machen?
		}
		
		long delayMilliseconds = this.beatTimeManager.calcTimeframeDelayByHistory();
		if(delayMilliseconds == OLD2_BeatHistory.NOT_ENOUGH_DATA) {
			delayMilliseconds = this.beatTimeManager.calcTimeframeDelayByOwnVelocity(this.timestampLastMessageSent);
		}
		
		if(BeatStabilizerDebuggerGUiApp.DEBUG != null) {
			BeatStabilizerDebuggerGUiApp.DEBUG.displayNextGreenFrameWindow("[???-"+delayMilliseconds+"]");
			BeatStabilizerDebuggerGUiApp.DEBUG.blinkSent(isArtificialMessage);
		}
		
		this.openDelayWindow(delayMilliseconds);
		
		this.broadcastToOutgoingModules(message);
		this.timestampLastMessageSent  = System.currentTimeMillis();
	}

	public void destroy() { // FIXME move to public API, as other modules probably need to be destroyed as well (stop threads)
		this.stopTimer();
	}
	
	@Override
	public final void receive(final ShortMessage message, final Transmitable transmitable) {
		LOG.debug("receive(message="+MidiUtil.toString(message)+", transmitable="+transmitable+")");
		this.beatHistory.addBeat();
		if(BeatStabilizerDebuggerGUiApp.DEBUG != null) {
			BeatStabilizerDebuggerGUiApp.DEBUG.blinkRawReceive();
		}
		
		// FIXME check if it is too early! stop timer, compute time left to enter beginning of timeframe window!
		boolean isSendingTooEarly = false;
		if(isSendingTooEarly == false) {
			this.stopTimer();
			this.startDelayWindowAndBroadcast(message, false);
		} else { // (isSendingTooEarly == true)
			
		}
	}

	private void openDelayWindow(long milliseconds) {
		LOG.debug("openDelayWindow(milliseconds=" + milliseconds + ")");
		
		if(this.timer != null) {
			throw new RuntimeException("this.timer != null");
		}
		this.timer = new Timer();
		this.timer.schedule(new TimerTaskWrapperX(this), milliseconds);
	}
	
	@Override
	public void onTimerComplete() {
		LOG.warn("onTimerComplete() ... creating delayed message");
		this.stopTimer();
		this.startDelayWindowAndBroadcast(this.createArtificialMessage(), true);
	}
	
	private void stopTimer() {
		if(this.timer != null) {
			LOG.debug("stopTimer() ... really stopping ;)");
			this.timer.cancel();
			this.timer = null;
		}
	}
	
	private ShortMessage createArtificialMessage() {
		return new ControllerMessage(1, 1, 50).build();
	}
	
	
	@Override
	public String toString() {
		return "BeatStabilizerModule[]";
	}
	
}
